#ifndef EVQ_H
#define EVQ_H
/*
 *  Copyright 2021-2025 Michael T. Richter as Half-Baked Software
 *  This program is free software. It comes without any warranty, to the extent
 *  permitted by applicable law. You can redistribute it and/or modify it under
 *  the terms of the Do What The Fuck You Want To Public License, Version 2, as
 *  published by Sam Hocevar. See the file COPYING or http://www.wtfpl.net/ for
 *  more details.
 */
/** @file
 *  @brief EVQ component of the COROS coroutine-OS.
 *
 *  EVQ is a sophisticated event queue for COROS.  It is used to decouple
 *  components in a system by having them communicate solely in terms of
 *  events.  Events can be seen as simple "wake up" calls or as
 *  sophisticated messages.  Event handlers can be simple callback functions
 *  or full-blown, independent coroutines (using the CO component).
 *
 *  Events are placed into an event queue.  Event queues are placed into a tree
 *  structured event environment.  Posting an event to an event queue makes it
 *  processed by any event queue higher in the chain; similarly, telling an
 *  event queue to process events will process ALL triggered events in ALL
 *  child queues.  This tree structure permits componentization where
 *  standalone components post messages to their own queues that are linked
 *  as children into a system-wide main queue, keeping all internal operational
 *  details to themselves.  When it is vital that a particular component
 *  processes its own messages without other components intervening, having
 *  a child queue get processed accomplishes this end.
 *
 *  Event queues are first class entities and can be passed around from task to
 *  task, even in messages, making Pi Calculus-like operations plausible as
 *  well.
 *
 *  Event queues are managed by these functions:
 *  - evq_create_queue();
 *  - evq_attach_queue();
 *  - evq_detach_queue();
 *
 *  Persistent events are managed by these functions:
 *  - evq_create_fn_event();
 *  - evq_create_co_event();
 *  - evq_post_event();
 *  - evq_cancel_event();
 *  - evq_query_time_remaining();
 *  - evq_destroy_event();
 *
 *  Note that evq_create_fn_event is for normal callback events while
 *  evq_create_co_event is for coroutine events.
 *
 *  Ad-hoc callback events are managed by these functions:
 *  - evq_queue_now();
 *  - evq_queue_in();
 *  - evq_queue_every();
 *
 *  Ad-hoc coroutine events are managed by these functions:
 *  - evq_resume_now();
 *  - evq_resume_in();
 *  - evq_resume_every();
 *
 *  Events are processed by these functions:
 *  - evq_process();
 *  - evq_process_for();
 *  - evq_process_one_tick();
 *
 *  The user must provide the following function implementation.
 *  - evq_get_tick();
 */

// Required headers.
#include <stdint.h>

#include "co.h"

typedef uint32_t delay_ms;                ///< type used in delays
typedef uint64_t tick_ms;                 ///< type used in snapshots
typedef struct _evq_queue_t *evq_queue_t; ///< Opaque data type for queues.
typedef struct _evq_event_t *evq_event_t; ///< Opaque data type for events.
typedef void (*evq_callback)(void *);     ///< Function type for callbacks.
typedef void (*evq_idle)(void);           ///< Function type for idle callbacks.

tick_ms evq_get_tick(void);

/*! Create a queue for the lifetime of the program.
 *
 *  This function creates a queue object (which is never released for the run
 *  time of the program), returning an opaque data type for use as a handle.
 *
 *  @result The handle needed for all later operations on the queue.
 */
evq_queue_t evq_create_queue(void);

/*! Attach a child queue to a parent one in a chain.
 *
 *  To help in modularity, event queues can be module-specific and attached
 *  together in a chain.  Attached queues form a linked list.  When processing
 *  the topmost queue is processed, then, if there is a link, that is processed,
 *  and so on until all attached queues have been processed.
 *
 *  @param[in] parent   The uplink queue which gets processed first.
 *  @param[in] child    The downlink queue which gets processed afterward.
 *  @note   There is a slight computational cost to chained queues in the form
 *          of an extra pointer redirection and a recursive function call for
 *          each additional member.
 */
void evq_attach_queue(evq_queue_t parent, evq_queue_t child);

/*! Remove a child queue from a chain.
 *
 *  This function removes a child queue from its place in a chained set of
 *  queues.
 *
 *  @param[in] child    The downlink queue to be removed.
 */
void evq_detach_queue(evq_queue_t child);

/*! Manually create an event with particular characteristics.
 *
 *  This API creates an event for later usage.  It is a low-level interface to
 *  the event queue system useful mainly for preparing signals for exceptional
 *  events and such.
 *
 *  @param[in] delay    The delay in milliseconds before the event callback is
 *                      called.
 *  @param[in] period   For periodic events, the separation between callback
 *                      firings.
 *  @param[in] cb       The callback function to be called when firing.
 *  @param[in] data     The data to be passed to the callback function.
 *  @note   The various queue*() functions make use of this function behind the
 *          scenes.
 */
evq_event_t evq_create_fn_event(delay_ms delay, delay_ms period, evq_callback cb, void *data);

/*! Fill a previously-created event with modified elements.
 *
 *  This API fills event data for later usage.  It is a low-level interface to
 *  the event queue system useful mainly for preparing signals for exceptional
 *  events and such.
 *
 *  @param[in] e        The event object being filled.
 *  @param[in] delay    The delay in milliseconds before the event callback is
 *                      called.
 *  @param[in] period   For periodic events, the separation between callback
 *                      firings.
 *  @param[in] cb       The callback function to be called when firing.
 *  @param[in] data     The data to be passed to the callback function.
 *  @note   The various queue*() functions make use of this function behind the
 *          scenes.
 */
void evq_fill_fn_event(evq_event_t e, delay_ms delay, delay_ms period, evq_callback cb, void *data);

/*! Manually create a coroutine event with particular characteristics.
 *
 *  This API creates an event for later usage.  It is a low-level interface to
 *  the event queue system useful mainly for preparing signals for exceptional
 *  events and such.
 *
 *  @param[in] delay    The delay in milliseconds before the event coroutine is
 *                      resumed.
 *  @param[in] period   For periodic events, the separation between callback
 *                      firings.
 *  @param[in] cb       The callback coroutine to be resumed when firing.
 *  @param[in] data     The data to be passed to the callback coroutine.
 *  @note   The various Resume*() functions make use of this function behind the
 *          scenes.
 */
evq_event_t evq_create_co_event(delay_ms delay, delay_ms period, co_t co, void *data);

/*! Fill a previously-created event with modified elements.
 *
 *  This API fills event data for later usage.  It is a low-level interface to
 *  the event queue system useful mainly for preparing signals for exceptional
 *  events and such.
 *
 *  @param[in] e        The event object being filled.
 *  @param[in] delay    The delay in milliseconds before the event callback is
 *                      called.
 *  @param[in] period   For periodic events, the separation between callback
 *                      firings.
 *  @param[in] cb       The callback function to be called when firing.
 *  @param[in] data     The data to be passed to the callback function.
 *  @note   The various queue*() functions make use of this function behind the
 *          scenes.
 */
void evq_fill_co_event(evq_event_t e, delay_ms delay, delay_ms period, co_t co, void *data);

/*! Manually post an event for processing.
 *
 *  This function will manually insert an event (callback or coroutine)
 *  according to its delay and period data established at creation into a given
 *  event queue.
 */
void evq_post_event(evq_queue_t q, evq_event_t e);

/*! Cancel an event that is queued.
 *
 *  This function removes an event from its queued location but does not delete
 *  the event object.
 *
 *  @param[in] e    The event to be cancelled.
 *  @note   DO NOT EVER TRY TO CANCEL A NON-PERIODIC EVENT WHICH HAS ALREADY
 *          FIRED!  THIS WILL CORRUPT MEMORY!
 */
void evq_cancel_event(evq_event_t e);

/*! Query the time remaining on a deferred event.
 *
 *  This function returns the number of milliseconds before an event will fire.
 *
 *  @param[in] e    The event to be cancelled.
 *  @note   DO NOT EVER TRY TO QUERY A NON-PERIODIC EVENT WHICH HAS ALREADY
 *          FIRED!  THIS WILL CORRUPT MEMORY!
 */
delay_ms evq_query_time_remaining(evq_event_t e);

/*! Destroy an event object.
 *
 *  This function cancels (if running) an event, and then reclaims the memory it
 *  uses.
 *
 *  @param[in] e    The event to be deleted.
 *  @note   THIS FUNCTION IS AUTOMATICALLY CALLED AFTER AN IMMEDIATE OR DEFFERED
 *          EVENT HAS FIRED.  IT IS AN ERROR TO CALL IT AFTERWARD!
 */
void evq_destroy_event(evq_event_t e);

/*! Create and queue a callback event for processing now.
 *
 *  Immediately schedules an event to be run next time one of the processing
 *  is called.
 *
 *  @param[in] q    The queue to place the event on.
 *  @param[in] cb   The callback function to be called.
 *  @param[in] data The data to be passed to the callback function.
 *  @result The event object created.
 *  @note   The created event is as if called with
 *          evq_create_fn_event(0, 0, cb, data);
 */
evq_event_t evq_queue_now(evq_queue_t q, evq_callback cb, void *data);

/*! Create and queue a callback event for processing at a later time.
 *
 *  Immediately schedules an event to be run next time one of the processing
 *  is called.
 *
 *  @param[in] q        The queue to place the event on.
 *  @param[in] cb       The callback function to be called.
 *  @param[in] data     The data to be passed to the callback function.
 *  @param[in] delay    The number of milliseconds to wait before firing the
 *                      event.
 *  @result The event object created.
 *  @note   The created event is as if called with
 *          evq_create_fn_event(delay, 0, cb, data);
 */
evq_event_t evq_queue_in(evq_queue_t q, evq_callback cb, void *data, delay_ms delay);

/*! Create and queue a callback event for processing periodically.
 *
 *  Immediately schedules an event to be run next time one of the processing
 *  is called.
 *
 *  @param[in] q        The queue to place the event on.
 *  @param[in] cb       The callback function to be called.
 *  @param[in] data     The data to be passed to the callback function.
 *  @param[in] period   The initial delay and the number of milliseconds between
 *                      firings.
 *  @result The event object created.
 *  @note   The created event is as if called with
 *          evq_create_fn_event(period, period, cb, data);
 */
evq_event_t evq_queue_every(evq_queue_t q, evq_callback cb, void *data, delay_ms period);


/*! Create and queue a coroutine event for processing now.
 *
 *  Immediately schedules an event to be run next time one of the processing
 *  is called.
 *
 *  @param[in] q    The queue to place the event on.
 *  @param[in] co   The coroutine function to be called.
 *  @param[in] data The data to be passed to the coroutine.
 *  @result The event object created.
 *  @note   The created event is as if called with
 *          evq_create_co_event(0, 0, co, data);
 */
evq_event_t evq_resume_now(evq_queue_t q, co_t co, void *data);

/*! Create and queue a coroutine event for processing now.
 *
 *  Immediately schedules an event to be run next time one of the processing
 *  is called.
 *
 *  @param[in] q    The queue to place the event on.
 *  @param[in] co   The coroutine function to be called.
 *  @param[in] data The data to be passed to the coroutine.
 *  @result The event object created.
 *  @note   The created event is as if called with
 *          evq_create_co_event(delay, 0, co, data);
 */
evq_event_t evq_resume_in(evq_queue_t q, co_t co, void *data, delay_ms delay);

/*! Create and queue a coroutine event for processing now.
 *
 *  Immediately schedules an event to be run next time one of the processing
 *  is called.
 *
 *  @param[in] q    The queue to place the event on.
 *  @param[in] co   The coroutine function to be called.
 *  @param[in] data The data to be passed to the coroutine.
 *  @result The event object created.
 *  @note   The created event is as if called with
 *          evq_create_co_event(period, period, co, data);
 */
evq_event_t evq_resume_every(evq_queue_t q, co_t co, void *data, delay_ms period);

/*! Start an event processing loop that never returns.
 *
 *  A call to evq_process should never return.  (If it does it signals an
 *  error of some kind!)  Once started the event queue is processed endlessly
 *  until program termination.
 *
 *  @param[in] q    The root queue to process.
 *  @note   In general queues should be created and attached before this loop
 *          is started.  Any nested queues that aren't attached should not be
 *          serviced with this function call.
 */
void evq_process(evq_queue_t q, evq_idle i);

/*! Start processing events, terminating after a given number of milliseconds.
 *
 *  This function works as per evq_process() but will return after a given
 *  number of milliseconds to permit other processing.  It may be called
 *  multiple times, and subordinate queues can be added and removed between
 *  invocations at the user's desire.
 *
 *  @param[in] q    The root queue to process.
 *  @param[in] ms   The root queue to process.
 */
void evq_process_for(evq_queue_t q, delay_ms ms, evq_idle i);

/*! Process all currently-due events, then return.
 *
 *  This function works as per evq_process() but will fire off all currently
 *  due events and then return. It may be called multiple times, and subordinate
 *  queues can be added and removed between invocations at the user's desire.
 *
 *  @param[in] q    The root queue to process.
 */
void evq_process_one_tick(evq_queue_t q, evq_idle i);

tick_ms evq_get_tick(void);

#endif //EVQ_H
